// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('goog.labs.Thenable');



/**
 * Provides a more strict interface for Thenables in terms of
 * http://promisesaplus.com for interop with {@see goog.labs.Promise}.
 *
 * @interface
 * @template TYPE
 */
goog.labs.Thenable = function() {};


/**
 * Adds callbacks that will operate on the result of the Thenable, returning a
 * new child Promise.
 *
 * If the Thenable is fulfilled, the {@code onFulfilled} callback will be
 * invoked with the fulfillment value as argument, and the child Promise will
 * be fulfilled with the return value of the callback. If the callback throws
 * an exception, the child Promise will be rejected with the thrown value
 * instead.
 *
 * If the Thenable is rejected, the {@code onRejected} callback will be invoked
 * with the rejection reason as argument, and the child Promise will be rejected
 * with the return value of the callback or thrown value.
 *
 * @param {(function(this:THIS, TYPE):
 *          (RESULT|goog.labs.Thenable.<RESULT>|Thenable))=} opt_onFulfilled A
 *     function that will be invoked with the fulfillment value if the Promise
 *     is fullfilled.
 * @param {(function(*): *)=} opt_onRejected A function that will be invoked
 *     with the rejection reason if the Promise is rejected.
 * @param {THIS=} opt_context An optional context object that will be the
 *     execution context for the callbacks. By default, functions are executed
 *     with the default this.
 * @return {!goog.labs.Promise.<RESULT>} A new Promise that will receive the
 *     result of the fulfillment or rejection callback.
 * @template RESULT,THIS
 */
goog.labs.Thenable.prototype.then = function(opt_onFulfilled, opt_onRejected,
    opt_context) {};


/**
 * An expando property to indicate that an object implements
 * goog.labs.Thenable.
 *
 * {@see addImplementation}.
 *
 * @const
 */
goog.labs.Thenable.IMPLEMENTED_BY_PROP =
    '$goog_labs_Thenable';


/**
 * Marks a given class (constructor) as an implementation of Thenable, so
 * that we can query that fact at runtime. The class must have already
 * implemented the interface.
 * Exports a 'then' method on the constructor prototype, so that the objects
 * also implement the extern {@see Thenable} interface for interop with
 * other Promise implementations.
 * @param {function(new:goog.labs.Thenable,...[?])} ctor The class constructor.
 *     The corresponding class must have already implemented the interface.
 */
goog.labs.Thenable.addImplementation = function(ctor) {
  goog.exportProperty(ctor.prototype, 'then', ctor.prototype.then);
  if (COMPILED) {
    ctor.prototype[goog.labs.Thenable.IMPLEMENTED_BY_PROP] = true;
  } else {
    // Avoids dictionary access in uncompiled mode.
    ctor.prototype.$goog_labs_Thenable = true;
  }
};


/**
 * @param {*} object
 * @return {boolean} Whether a given instance implements
 *     {@code goog.labs.Thenable}. The class/superclass of the instance must
 *     call {@code addImplementation}.
 */
goog.labs.Thenable.isImplementedBy = function(object) {
  if (!object) {
    return false;
  }
  try {
    if (COMPILED) {
      return !!object[goog.labs.Thenable.IMPLEMENTED_BY_PROP];
    }
    return !!object.$goog_labs_Thenable;
  } catch (e) {
    // Property access seems to be forbidden.
    return false;
  }
};
